﻿#include "InsourcedTranslator.h"

#include <QMetaEnum>
#include <QFile>
#include <QByteArray>
#include <QTextStream>
#include <QDataStream>
#include <QDir>
#include <QTextCodec>
#include <QRegularExpression>

#pragma execution_character_set("utf-8")

struct TranslationItem {
    QString context;
    QString sourceText;
    QString disambiguation;
    int n{-1};
};

struct InsourcedTranslatorPrivate {
    inline static QMap<QString, QMap<QString, QString>> textMap;
    inline static QList<TranslationItem> untranslatedItems;
    inline static QHash<QLocale, QByteArray> languageMap;
};

InsourcedTranslator::~InsourcedTranslator() {}

InsourcedTranslator::InsourcedTranslator(QObject *parent) : QTranslator {parent} { }

void InsourcedTranslator::SetLanguage(const QLocale::Language &language, const QLocale::Country &country) {
    SetLanguage(QLocale(language, country));
}

void InsourcedTranslator::SetLanguage(const QLocale &locale) {
    const auto translator = new InsourcedTranslator;

    const auto &qmFileData = InsourcedTranslatorPrivate::languageMap.value(locale, QByteArray());
    if(!qmFileData.isEmpty()) {
        const bool success = translator->load((uchar*)qmFileData.data(), qmFileData.count());
        if(!success) {
            HI_DEBUG << "Translator load failed";
        }
    } else {
        const QString baseName = qApp->applicationName() + "_" + QLocale(locale).name() +".qm";
        const QString &fileName = qApp->applicationDirPath() + "/" + baseName;
        HI_DEBUG << fileName;
        const bool success = translator->load(fileName);
        if(!success) {
            HI_DEBUG << "Translator load failed";
        }
    }

    qApp->installTranslator(translator);
}

QList<QLocale> InsourcedTranslator::AvailableLanguages() {
    return InsourcedTranslatorPrivate::languageMap.keys();
}

QString InsourcedTranslator::translate(const char *context, const char *sourceText, const char *disambiguation, int n) const {
    //HI_DEBUG << QString("%1:%2").arg(context).arg(sourceText);
    QStringList ignoredContexts = {
        //"QShortcut",
        "QGuiApplication",
        "QLibrary"
    };

    if(!ignoredContexts.contains(context)) {
        const auto &translate = SearchTranslation(context, sourceText, disambiguation, n);
        if(!translate.found) {
            AddUntranslatedItem(context, sourceText, disambiguation, n);
        } else if(!translate.text.isEmpty()) {
            return translate.text;
        }
    }

    return sourceText;
}

InsourcedTranslator::TranslateResult InsourcedTranslator::SearchTranslation(const char *context, const char *sourceText, const char *disambiguation, int n) const {
    const auto &language = this->language();
    //HI_DEBUG << language;
    TranslateResult result;
    const auto &columnMap = InsourcedTranslatorPrivate::textMap.value(language);
    if(columnMap.isEmpty()) {
        return result;
    }


    QStringList items {context, sourceText, disambiguation};
    const auto &key = items.join("-");
    //HI_DEBUG << key;
    result.found = columnMap.contains(key);
    result.text = columnMap.value(key);
    return result;
}

void InsourcedTranslator::AddUntranslatedItem(const char *context, const char *sourceText, const char *disambiguation, int n) const {
    QString actualContext = context;
    QString actualSourceText = sourceText;
    QString actualDisambiguation = disambiguation;

    bool hasQuote = actualContext.contains(",") || actualContext.contains("，");
    hasQuote |= actualSourceText.contains(",") || actualSourceText.contains("，");

    if(hasQuote) {
        actualContext = QString("\"%1\"").arg(actualContext);
        actualSourceText = QString("\"%1\"").arg(actualSourceText);
        actualDisambiguation = QString("\"%1\"").arg(actualDisambiguation);
    }

    const auto &key = QStringList({actualContext, actualSourceText, actualDisambiguation}).join("-");
    static QSet<QString> keySet;
    if(keySet.contains(key)) {
        return ;
    }
    keySet.insert(key);

    HI_DEBUG << QStringList({actualContext, actualSourceText, actualDisambiguation}).join(',') + ",";
    TranslationItem item;
    item.context = actualContext;
    item.sourceText = actualSourceText;
    item.disambiguation = actualDisambiguation;
    item.n = n;
    InsourcedTranslatorPrivate::untranslatedItems.append(item);
}

void InsourcedTranslator::SaveUntranslatedItemToFile() {
    QString filePath = QApplication::applicationDirPath();
    filePath += "/untranslated_texts.csv";

    QFile file(filePath);
    if(!file.open(QFile::Text | QFile::ReadWrite)) {
        HI_DEBUG << "Failed to open file!";
        return ;
    }

    QTextStream stream(&file);
    stream.setAutoDetectUnicode(true);
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
    stream.setCodec("UTF-8");
#else
    stream.setEncoding(QStringConverter::Utf8);
#endif

    for(const auto &item : InsourcedTranslatorPrivate::untranslatedItems) {
        QStringList columns;
        columns << item.context << item.sourceText << item.disambiguation;
        stream << columns.join(",") + "," << "\n";
    }
}

QLocale LocaleFromUiLanguage(const QString &uiLanguage) {
    //HI_DEBUG << uiLanguage;
    const auto &availableLocales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);
    for(const auto &locale : availableLocales) {
        //HI_DEBUG << locale.name();
        if(locale.uiLanguages().contains(uiLanguage)) {
            return locale;
        }
    }

    return QLocale::c();
}

QByteArray GenerateQmFile(const QLocale &locale, bool toFile = false);

void InsourcedTranslator::LoadTranslations() {
    const auto &filePath = ":/translations/text_translations.csv";
    QFile file(filePath);
    if(!file.open(QFile::ReadOnly | QFile::Text)) {
        HI_DEBUG << "Failed to open file";
        return ;
    }

    connect(qApp, &QApplication::aboutToQuit, [=] {
        SaveUntranslatedItemToFile();
    });

    QByteArray data = file.readAll();
    file.close();

    QTextStream stream(data, QIODevice::ReadOnly | QIODevice::Text);
    stream.setAutoDetectUnicode(true);
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
    stream.setCodec("UTF-8");
#else
    stream.setEncoding(QStringConverter::Utf8);
#endif

    const auto &firstLine = stream.readLine().simplified();
    const auto &titles = firstLine.split(",");

    HI_DEBUG << titles;

    bool valid = (titles.count() >= 4);
    if(valid) {
        valid &= (titles.at(0) == "Context");
        valid &= (titles.at(1) == "SourceText");
        valid &= (titles.at(2) == "Disambiguation");
    }

    if(!valid) {
        HI_DEBUG << "Invalid file format";
        return ;
    }

    QStringList languages;
    for(int i = 3; i < titles.count(); ++i) {
        const QString &localeName = titles.at(i);
        //const auto &locale = LocaleFromUiLanguage(localeName);
        const auto &locale = QLocale(localeName);
        languages.append(locale.name());
        const auto &qmFileData = GenerateQmFile(locale);
        InsourcedTranslatorPrivate::languageMap.insert(locale, qmFileData);
    }

    for(const auto &language : languages) {
        InsourcedTranslatorPrivate::textMap.insert(language, QMap<QString, QString>());
    }

    while(!stream.atEnd()) {
        const auto &line = stream.readLine().simplified();
        if(line.isEmpty() || line.startsWith("#")) {
            continue;
        }

        bool hasQuote = line.contains('"');
        const auto &columns = line.split(hasQuote ? "\"," : ",");

        auto context = columns.at(0);
        auto sourceText = columns.at(1);
        auto disambiguation = columns.at(2);
        static QRegularExpression regexp("(^[\"])|([\"]$)");
        if (hasQuote) {
            context.remove(regexp);
            sourceText.remove(regexp);
            disambiguation.remove(regexp);
        }
        
        const auto &key = context + "-" + sourceText + "-" + disambiguation;

        for(int i = 3; i < columns.count(); ++i) {
            auto tranlateText = columns.at(i);
            if (hasQuote) {
                tranlateText.remove(regexp);
            }
            const auto &language = languages.at(i-3);
            InsourcedTranslatorPrivate::textMap[language].insert(key, tranlateText);
        }
    }
}

QByteArray GenerateQmFile(const QLocale &locale, bool toFile) {
    using U08 = quint8;
    const QList<U08> header =
        {
         U08{0x3C},
         U08{0xB8},
         U08{0x64},
         U08{0x18},
         U08{0xCA},
         U08{0xEF},
         U08{0x9C},
         U08{0x95},
         U08{0xCD},
         U08{0x21},
         U08{0x1C},
         U08{0xBF},
         U08{0x60},
         U08{0xA1},
         U08{0xBD},
         U08{0xDD},
         U08{0xA7},
         };

    QByteArray buffer;

    QDataStream stream(&buffer, QIODevice::WriteOnly);
    stream.setByteOrder(QDataStream::BigEndian);
    for(auto &byte : header) {
        stream << byte;
    }

    const auto &localeName = locale.name();
    stream.writeBytes(localeName.toLocal8Bit().constData(), localeName.count());

    if(toFile) {
        QString baseName = QString("%1_%2.qm").arg(QApplication::applicationName()).arg(locale.name());
        QString path = QApplication::applicationDirPath() + "/";
        //path += "translations/";
        QString filePath = path + baseName;

        QDir dir(path);
        if (!dir.exists()) {
            dir.mkpath(path);
        }

        QFile file(filePath);
        if (!file.open(QFile::WriteOnly)) {
            return buffer;
        }

        file.write(buffer);
        file.close();
    }

    //HI_DEBUG << buffer.toHex();
    return buffer;
}
